#!/usr/bin/env bash
# UOS-V20_1050u1e-aarch64
# 1) 系统补丁 -> 最新
# 2) OpenSSL 3.5.2（共享库）
# 3) OpenSSH（默认 10.0p2，自动处理 libfido2 缺包）
# 4) SSH 端口蓝绿切换到 20000–40000 随机可用端口
# 5) 自动修复 systemd 单元 & sysconfig（避免 $CRYPTO_POLICY/$PERMITROOTLOGIN 导致失败）
# 6) 输出最终版本与端口
set -euo pipefail

OPENSSH_VER="${1:-10.0p2}"
OPENSSL_VER="3.5.2"
OPENSSH_URL="https://mirrors.aliyun.com/pub/OpenBSD/OpenSSH/portable/openssh-${OPENSSH_VER}.tar.gz"
OPENSSL_URL="https://mirrors.aliyun.com/macports/distfiles/openssl3/openssl-${OPENSSL_VER}.tar.gz"

PREFIX_OPENSSL="/usr/local/openssl-${OPENSSL_VER}"
OPENSSL_LINK="/usr/local/openssl"
BUILD_DIR="/usr/local/src"
BACKUP_DIR="/usr/local/backup-ssh-$(date +%Y%m%d%H%M%S)"
MAKE_JOBS="${MAKE_JOBS:-$(nproc)}"

info(){ echo -e "\033[1;34m[INFO]\033[0m $*" >&2; }
warn(){ echo -e "\033[1;33m[WARN]\033[0m $*" >&2; }
err(){  echo -e "\033[1;31m[ERR ]\033[0m $*" >&2; }

need_root(){ [ "$(id -u)" -eq 0 ] || { err "请用 root 运行"; exit 1; }; }

detect_pkg(){
  if command -v apt >/dev/null 2>&1; then PKG="apt"
  elif command -v dnf >/dev/null 2>&1; then PKG="dnf"
  elif command -v yum >/dev/null 2>&1; then PKG="yum"
  else err "未找到 apt/dnf/yum"; exit 1; fi
  info "包管理器：$PKG"
}

update_system(){
  info "更新系统补丁到最新……"
  case "$PKG" in
    apt) export DEBIAN_FRONTEND=noninteractive; apt update; apt -y full-upgrade; apt -y autoremove --purge ;;
    dnf) dnf -y upgrade --refresh ;;
    yum) yum -y update ;;
  esac
  info "系统补丁更新完成。"
}

# 缺包自动跳过（解决 “No match for argument: fido2-devel”）
install_try(){
  case "$PKG" in
    apt)
      local to_install=()
      for p in "$@"; do apt-cache show "$p" >/dev/null 2>&1 && to_install+=("$p"); done
      [ "${#to_install[@]}" -gt 0 ] && apt -y install "${to_install[@]}" || true
      ;;
    dnf|yum)
      local mgr="$PKG"; local to_install=()
      for p in "$@"; do
        if "$mgr" list --available "$p" >/dev/null 2>&1 || "$mgr" list installed "$p" >/dev/null 2>&1; then
          to_install+=("$p")
        fi
      done
      [ "${#to_install[@]}" -gt 0 ] && "$mgr" -y install "${to_install[@]}" || true
      ;;
  esac
}

install_build_deps(){
  info "安装编译依赖（缺包自动跳过）……"
  case "$PKG" in
    apt)
      export DEBIAN_FRONTEND=noninteractive
      install_try build-essential wget curl ca-certificates tar pkg-config \
        zlib1g-dev libpam0g-dev libselinux1-dev libedit-dev \
        libfido2-dev libcbor-dev autoconf automake libtool
      ;;
    dnf|yum)
      install_try gcc gcc-c++ make wget curl ca-certificates tar pkgconfig \
        zlib-devel pam-devel libselinux-devel libedit-devel \
        libfido2-devel libcbor-devel autoconf automake libtool openssl-devel
      ;;
  esac
}

prepare_build_env(){ mkdir -p "$BUILD_DIR" "$BACKUP_DIR"; cd "$BUILD_DIR"; }

download_and_extract(){
  local url="$1"; local out="$2"
  info "下载: $url"
  (curl -fsSL "$url" -o "$out" || wget -O "$out" "$url") 2>&1 >&2
  local topdir; topdir="$(tar tzf "$out" | head -1 | cut -d/ -f1)"
  [ -n "$topdir" ] || { err "无法解析 tar 包目录结构"; exit 1; }
  rm -rf "$topdir"; tar xzf "$out"; printf '%s' "$topdir"
}

build_openssl(){
  info "构建 OpenSSL ${OPENSSL_VER}……"
  local tarball="openssl-${OPENSSL_VER}.tar.gz"
  local srcdir; srcdir="$(download_and_extract "$OPENSSL_URL" "$tarball")"
  cd "$srcdir"
  ./config --prefix="${PREFIX_OPENSSL}" --openssldir="${PREFIX_OPENSSL}/ssl" zlib || \
  ./Configure linux-generic64 --prefix="${PREFIX_OPENSSL}" --openssldir="${PREFIX_OPENSSL}/ssl" zlib
  make -j"${MAKE_JOBS}"; make install_sw install_ssldirs
  ln -sfn "${PREFIX_OPENSSL}" "${OPENSSL_LINK}"
  echo "${OPENSSL_LINK}/lib" > /etc/ld.so.conf.d/openssl-local.conf; ldconfig
  cd "$BUILD_DIR"
}

build_openssh(){
  info "构建 OpenSSH ${OPENSSH_VER}……"
  local tarball="openssh-${OPENSSH_VER}.tar.gz"
  local srcdir; srcdir="$(download_and_extract "$OPENSSH_URL" "$tarball")"
  cd "$srcdir"

  getent group sshd >/dev/null || groupadd -r sshd
  getent passwd sshd >/dev/null || useradd -r -g sshd -d /var/empty -s /sbin/nologin sshd || true
  mkdir -p /var/empty && chown root:root /var/empty && chmod 755 /var/empty

  export CPPFLAGS="-I${OPENSSL_LINK}/include"
  export LDFLAGS="-L${OPENSSL_LINK}/lib -Wl,-rpath,${OPENSSL_LINK}/lib"
  export PATH="${OPENSSL_LINK}/bin:${PATH}"
  export PKG_CONFIG_PATH="${OPENSSL_LINK}/lib/pkgconfig:${PKG_CONFIG_PATH:-}"

  local SK_FLAG="--without-security-key"
  if pkg-config --exists libfido2 2>/dev/null; then
    SK_FLAG=""; info "检测到 libfido2，启用 FIDO/U2F 支持。"
  else
    warn "未检测到 libfido2-devel，使用 ${SK_FLAG}。"
  fi

  ./configure --prefix=/usr/local \
    --sysconfdir=/etc/ssh \
    --with-ssl-dir="${OPENSSL_LINK}" \
    --with-pam \
    --with-privsep-path=/var/empty \
    ${SK_FLAG}

  make -j"${MAKE_JOBS}"; make install
  cd "$BUILD_DIR"
}

swap_sshd_binary(){
  info "备份并替换系统 ssh/sshd……"
  local NEW_SSHD="/usr/local/sbin/sshd"; [ -x "$NEW_SSHD" ] || NEW_SSHD="/usr/local/bin/sshd"
  [ -x "$NEW_SSHD" ] || { err "未找到新 sshd"; exit 1; }
  for bin in /usr/sbin/sshd /usr/bin/ssh /usr/bin/scp; do [ -x "$bin" ] && cp -a "$bin" "${BACKUP_DIR}/" || true; done
  install -m 0755 "$NEW_SSHD" /usr/sbin/sshd
  [ -x /usr/local/bin/ssh ] && install -m 0755 /usr/local/bin/ssh /usr/bin/ssh || true
  [ -x /usr/local/bin/scp ] && install -m 0755 /usr/local/bin/scp /usr/bin/scp || true
}

# ========= 修复服务单元 & 配置 =========
ensure_hostkeys(){
  # 若没有主机密钥则生成（否则 sshd 会直接退出）
  if ! ls /etc/ssh/ssh_host_*key >/dev/null 2>&1; then
    info "未发现主机密钥，执行 ssh-keygen -A"
    ssh-keygen -A
  fi
}

fix_sshd_unit_dropin(){
  # 一些系统的 sshd.service 会传入 $CRYPTO_POLICY / $PERMITROOTLOGIN 导致编译版 sshd 退出
  # 建立 drop-in 覆盖，仅保留 -D 及 $OPTIONS（若 sysconfig 里设置）
  local d="/etc/systemd/system/sshd.service.d"
  mkdir -p "$d"
  cat > "$d/override.conf" <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/sbin/sshd -D $OPTIONS
EOF
  systemctl daemon-reload
}

sanitize_sysconfig(){
  # 将 /etc/sysconfig/sshd 里可能不兼容的变量清空或转成 server 可识别形式
  local f="/etc/sysconfig/sshd"
  [ -f "$f" ] || return 0
  cp -a "$f" "${f}.bak.$(date +%Y%m%d%H%M%S)"
  # 清掉 CRYPTO_POLICY / PERMITROOTLOGIN 行，保留 OPTIONS
  sed -ri 's/^\s*(CRYPTO_POLICY|PERMITROOTLOGIN)\s*=.*/\1=""/' "$f"
}

restart_sshd(){
  systemctl daemon-reload || true
  if systemctl status ssh >/dev/null 2>&1; then
    systemctl restart ssh
  elif systemctl status sshd >/dev/null 2>&1; then
    systemctl restart sshd
  else
    /usr/sbin/sshd -t && /usr/sbin/sshd
  fi
}

is_port_listening(){
  local port="$1"
  if command -v ss >/dev/null 2>&1; then
    ss -ltn | awk '{print $4}' | grep -q "[:.]${port}$"
  else
    netstat -ltn 2>/dev/null | awk '{print $4}' | grep -q "[:.]${port}$"
  fi
}

open_firewall_selinux(){
  local port="$1"
  if command -v semanage >/dev/null 2>&1; then
    semanage port -a -t ssh_port_t -p tcp "${port}" 2>/dev/null || \
    semanage port -m -t ssh_port_t -p tcp "${port}" 2>/dev/null || true
  fi
  if command -v firewall-cmd >/dev/null 2>&1; then
    firewall-cmd --add-port="${port}"/tcp --permanent 2>/dev/null || true
    firewall-cmd --reload 2>/dev/null || true
  elif command -v iptables >/dev/null 2>&1; then
    iptables -C INPUT -p tcp --dport "${port}" -j ACCEPT 2>/dev/null || \
    iptables -I INPUT -p tcp --dport "${port}" -j ACCEPT || true
    if command -v service >/dev/null 2>&1 && service iptables save >/dev/null 2>&1; then service iptables save || true; fi
  fi
}

_current_ssh_port(){
  local conf="/etc/ssh/sshd_config"; local p
  p="$(awk 'tolower($1)=="port"{print $2; exit}' "$conf" 2>/dev/null || true)"
  echo "${p:-22}"
}

pick_random_port(){
  local p
  for _ in $(seq 1 20); do
    p="$(shuf -i 20000-40000 -n 1)"
    if ! is_port_listening "$p"; then echo -n "$p"; return 0; fi
  done
  err "在区间 20000-40000 内未能找到可用端口"; exit 1
}

change_port_with_safety(){
  local conf="/etc/ssh/sshd_config"
  local conf_bak="/etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)"
  local old_port new_port
  old_port="$(_current_ssh_port)"
  new_port="$(pick_random_port)"
  cp -a "$conf" "$conf_bak"

  # 并行监听：保留旧端口 + 新端口
  open_firewall_selinux "$new_port"
  sed -ri '/^\s*#?\s*Port\s+.*$/d' "$conf"
  echo "Port ${old_port}" >> "$conf"
  echo "Port ${new_port}" >> "$conf"

  # 单元修复 & sysconfig 清理 & hostkeys 保证存在
  fix_sshd_unit_dropin
  sanitize_sysconfig
  ensure_hostkeys

  # 配置校验
  if ! /usr/sbin/sshd -t 2>/tmp/sshd_check.err; then
    cat /tmp/sshd_check.err >&2
    cp -a "$conf_bak" "$conf"
    err "sshd 配置校验失败，已回滚。"
  fi

  restart_sshd

  # 等待新端口监听
  for i in $(seq 1 15); do is_port_listening "$new_port" && break; sleep 1; done
  if ! is_port_listening "$new_port"; then
    warn "新端口 ${new_port} 未监听，回滚到旧端口 ${old_port}。"
    cp -a "$conf_bak" "$conf"; restart_sshd
    err "切换端口失败。"
  fi

  # 切换：仅保留新端口
  sed -ri '/^\s*#?\s*Port\s+.*$/d' "$conf"; echo "Port ${new_port}" >> "$conf"

  if ! /usr/sbin/sshd -t 2>/tmp/sshd_check2.err; then
    cat /tmp/sshd_check2.err >&2
    cp -a "$conf_bak" "$conf"; restart_sshd
    err "最终配置校验失败，已回滚。"
  fi
  restart_sshd
  FINAL_SSH_PORT="${new_port}"
  export FINAL_SSH_PORT
  info "SSH 已切换到新端口：${FINAL_SSH_PORT}"
}

show_versions(){
  echo "=========== 最终版本 & 端口 =========="
  echo -n "OpenSSH: " ; /usr/sbin/sshd -V 2>&1 | head -n1
  echo -n "OpenSSL: " ; openssl version -a | head -n1
  echo "SSH Port: ${FINAL_SSH_PORT}"
  echo "======================================"
}

main(){
  need_root
  detect_pkg
  update_system
  install_build_deps
  prepare_build_env
  build_openssl
  build_openssh
  swap_sshd_binary

  change_port_with_safety
  show_versions

  info "现在请使用：ssh -p ${FINAL_SSH_PORT} <user>@<host> 测试远程连接。"
}
main "$@"
